<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AutoAPI 自动化测试框架</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        :root {
            /* 优化后的小清新配色方案 - 颜色深度增加 */
            --bg-light: #eef4f8;
            --bg-lighter: #e0edf5;
            --bg-card: #ffffff;
            --text-primary: #2a2a2a;
            --text-secondary: #5a6770;
            --accent: #4a8c9c;
            --accent-hover: #3a7c8c;
            --border: #c0d8e0;
            --success: #6ab28a;
            --warning: #e9a947;
            --danger: #e85b5b;
            --code-bg: #f0f5fa;
            --menu-width: 280px;
            --header-height: 60px;
            --leaf-green: #8dc8a9;
            --sky-blue: #8cb5c8;
            --soft-pink: #e8b6b6;
            --transition: all 0.3s ease;
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
        }

        body {
            background-color: var(--bg-light);
            color: var(--text-primary);
            display: flex;
            min-height: 100vh;
            overflow-x: hidden;
            background-image: linear-gradient(135deg, #e0edf5 0%, #eef4f8 100%);
        }

        /* 侧边栏样式 - 优化后 */
        .sidebar {
            width: var(--menu-width);
            background-color: var(--bg-lighter);
            border-right: 1px solid var(--border);
            height: 100vh;
            overflow-y: auto;
            position: sticky;
            top: 0;
            transition: var(--transition);
            box-shadow: 2px 0 10px rgba(0, 0, 0, 0.05);
        }

        .sidebar-header {
            padding: 20px;
            border-bottom: 1px solid var(--border);
            display: flex;
            align-items: center;
            justify-content: space-between;
            /* background-color: rgba(255, 255, 255, 0.9); */
            background-color: var(--bg-lighter);
        }

        .sidebar::-webkit-scrollbar {
            width: 6px;
        }

        .sidebar::-webkit-scrollbar-track {
            background: var(--bg-lighter);
        }

        .sidebar::-webkit-scrollbar-thumb {
            background-color: var(--accent);
            border-radius: 4px;
        }

        .sidebar::-webkit-scrollbar-thumb:hover {
            background-color: var(--accent-hover);
        }

        .logo {
            font-size: 30px;
            font-weight: bold;
            color: var(--accent);
            display: flex;
            align-items: center;
            gap: 20px;

        }

        .logo i {
            font-size: 30px;
            color: var(--leaf-green);
        }

        .menu-toggle {
            background: none;
            border: none;
            color: var(--text-secondary);
            font-size: 18px;
            cursor: pointer;
            display: none;
            z-index: 900;
        }

        .menu {
            padding: 15px 0;
        }

        .menu-item {
            list-style: none;
        }

        .menu-link {
            display: flex;
            align-items: center;
            padding: 11px 20px;
            color: var(--text-primary);
            text-decoration: none;
            transition: var(--transition);
            position: relative;
            border-radius: 0 30px 30px 0;
            margin: 5px 10px;
        }

        .menu-link:hover {
            background-color: rgba(74, 140, 156, 0.15);
            color: var(--accent);
        }

        .menu-link.active {
            background-color: rgba(74, 140, 156, 0.2);
            color: var(--accent);
            border-left: 3px solid var(--accent);
        }

        .menu-link i {
            margin-right: 12px;
            width: 24px;
            text-align: center;
            color: var(--leaf-green);
        }

        .submenu {
            max-height: 0;
            overflow: hidden;
            transition: max-height 0.3s ease;
            background-color: rgba(141, 200, 169, 0.15);
            border-radius: 0 0 15px 15px;
            margin: 0 20px;
        }

        .submenu.open {
            max-height: 1000px;
        }

        .submenu-link {
            padding: 10px 20px 10px 50px;
            display: block;
            color: var(--text-secondary);
            text-decoration: none;
            transition: var(--transition);
        }

        .submenu-link:hover {
            color: var(--accent);
            background-color: rgba(74, 140, 156, 0.08);
        }

        .submenu-link.active {
            color: var(--accent);
            font-weight: 500;
        }

        .menu-arrow {
            margin-left: auto;
            transition: transform 0.3s;
            color: var(--sky-blue);
        }

        .menu-arrow.open {
            transform: rotate(90deg);
        }

        /* 主内容区域 - 优化后 */
        .main-content {
            flex: 1;
            padding: 30px;
            max-width: 1200px;
            margin: 0 auto;
            min-height: 100vh;
        }

        .content-header {
            margin-bottom: 30px;
            padding-bottom: 15px;
            border-bottom: 1px solid var(--border);
        }

        .content-header h1 {
            font-size: 28px;
            margin-bottom: 10px;
            color: var(--accent);
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .content-header h1 i {
            color: var(--leaf-green);
        }

        .content-header p {
            color: var(--text-secondary);
        }

        .content-card {
            background-color: var(--bg-card);
            border-radius: 12px;
            padding: 25px;
            margin-bottom: 25px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
            border: 1px solid var(--border);
            transition: var(--transition);
        }

        .content-card-two {
            background-color: var(--bg-card);
            border-radius: 12px;
            padding: 15px;
            margin-bottom: 15px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
            border: 1px solid var(--border);
            transition: var(--transition);
        }

        .content-card:hover {
            box-shadow: 0 6px 15px rgba(0, 0, 0, 0.12);
            transform: translateY(-3px);
        }

        .content-card h2 {
            font-size: 24px;
            margin-bottom: 20px;
            color: var(--accent);
            padding-bottom: 10px;
            border-bottom: 1px solid var(--border);
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .content-card h2 i {
            color: var(--sky-blue);
        }

        .content-card h3 {
            font-size: 20px;
            margin: 5px 0 10px;
            color: var(--accent);
        }

        .content-card h4 {
            font-size: 18px;
            margin: 20px 0 12px;
            color: var(--text-secondary);
        }

        p {
            margin-bottom: 15px;
            line-height: 1.6;
            color: var(--text-primary);
        }

        ul,
        ol {
            margin-left: 25px;
            margin-bottom: 20px;
        }

        li {
            margin-bottom: 8px;
            line-height: 1.5;
            color: var(--text-primary);
        }

        .code-block {
            background-color: var(--code-bg);
            border-radius: 8px;
            padding: 1em;
            margin-top: 1em;
            font-family: 'Courier New', monospace;
            overflow-x: auto;
            color: #4a8c9c;
            border: 1px solid var(--border);
        }

        .code-block code {
            display: block;
        }

        /* 表格样式 - 优化后 */
        .table-container {
            overflow-x: auto;
            /* margin: 25px 0; */
            /* border: 1px solid var(--border); */
            /* border-radius: 8px; */
            /* background: white; */
            /* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); */
        }

        .table-container thead th {
            padding: 12px 15px;
            text-align: left;
            color: var(--text-secondary);
        }

        table.styled-table {
            width: 100%;
            border-collapse: collapse;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            font-size: 14px;
            color: var(--text-primary);
            background-color: white;
        }

        .styled-table th,
        .styled-table td {
            padding: 12px 15px;
            border: 1px solid var(--border);
            text-align: left;
        }

        .styled-table th {
            background-color: rgba(141, 200, 169, 0.15);
            color: var(--accent);
            font-weight: 600;
        }

        .styled-table tbody tr:nth-child(even) {
            background-color: rgba(140, 181, 200, 0.08);
        }

        .styled-table tbody tr:hover {
            background-color: rgba(141, 200, 169, 0.2);
        }

        /* 警告框样式 - 优化后 */
        .alert {
            padding: 15px;
            border-radius: 8px;
            margin: 20px 0;
            display: flex;
            align-items: flex-start;
            border-left: 4px solid;
        }

        .alert i {
            margin-right: 12px;
            font-size: 20px;
            margin-top: 2px;
        }

        .alert-info {
            background-color: rgba(140, 181, 200, 0.2);
            border-left-color: var(--sky-blue);
            color: var(--text-primary);
        }

        .alert-warning {
            background-color: rgba(233, 169, 71, 0.2);
            border-left-color: var(--warning);
            color: var(--text-primary);
        }

        .alert-danger {
            background-color: rgba(232, 91, 91, 0.2);
            border-left-color: var(--danger);
            color: var(--text-primary);
        }

        /* 按钮样式 - 优化后 */
        .btn {
            display: inline-block;
            padding: 10px 20px;
            background-color: var(--accent);
            color: white;
            border: none;
            border-radius: 24px;
            cursor: pointer;
            font-weight: 500;
            transition: var(--transition);
            text-decoration: none;
            box-shadow: 0 2px 5px rgba(74, 140, 156, 0.25);
        }

        .btn:hover {
            background-color: var(--accent-hover);
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(74, 140, 156, 0.35);
        }

        .flex {
            display: flex;
            gap: 15px;
            flex-wrap: wrap;
            margin: 20px 0;
        }

        /* 图片容器 - 优化后 */
        .image-container {
            margin: 20px 0;
            text-align: center;
            border-radius: 12px;
            overflow: hidden;
            border: 1px solid var(--border);
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
            transition: var(--transition);
        }

        .image-container:hover {
            box-shadow: 0 6px 15px rgba(0, 0, 0, 0.12);
        }

        .image-container img {
            max-width: 100%;
            transition: transform 0.3s ease;
        }

        .image-container:hover img {
            transform: scale(1.02);
        }

        .image-container p {
            margin-top: 8px;
            font-size: large;
            color: var(--text-secondary);
            font-style: italic;
            padding: 10px;
            background: rgba(255, 255, 255, 0.85);
        }

        .badge {
            display: inline-block;
            padding: 4px 10px;
            border-radius: 20px;
            font-size: 12px;
            font-weight: bold;
            margin-right: 8px;
        }

        .badge-success {
            background-color: var(--success);
            color: white;
        }

        .badge-warning {
            background-color: var(--warning);
            color: white;
        }

        .badge-danger {
            background-color: var(--danger);
            color: white;
        }

        .feature-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
            gap: 20px;
            margin: 20px 0;
        }

        .feature-card {
            background-color: white;
            border-radius: 12px;
            padding: 20px;
            border-left: 4px solid var(--leaf-green);
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.08);
            transition: var(--transition);
        }

        .feature-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 8px 15px rgba(0, 0, 0, 0.12);
        }

        .feature-card h3 {
            margin-top: 0;
            color: var(--accent);
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .feature-card h3 i {
            color: var(--leaf-green);
        }

        /* 加载动画 */
        .content-loading {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 300px;
            color: var(--text-secondary);
        }

        .content-loading i {
            font-size: 48px;
            margin-bottom: 20px;
            animation: spin 2s linear infinite;
            color: var(--accent);
        }

        /* 报告图库 - 优化后 */
        .report-gallery {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
            gap: 25px;
            margin: 25px 0;
        }

        .report-card {
            border-radius: 12px;
            overflow: hidden;
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
            transition: var(--transition);
            background: white;
        }

        .report-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 8px 15px rgba(0, 0, 0, 0.12);
        }

        .report-img {
            height: 180px;
            background: linear-gradient(135deg, var(--leaf-green), var(--sky-blue));
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-size: 3rem;
        }

        .report-img img {
            width: 100%;
            height: 100%;
            object-fit: cover;
            transition: transform 0.3s ease;
        }

        .report-card:hover .report-img img {
            transform: scale(1.05);
        }

        .report-card .caption {
            padding: 15px;
            background-color: white;
            font-weight: 600;
            text-align: center;
            border-top: 1px solid var(--border);
            color: var(--accent);
        }

        /* 图片模态框样式 - 优化后 */
        .image-modal {
            display: none;
            position: fixed;
            z-index: 1000;
            left: 0;
            top: 0;
            width: 100vw;
            height: 100vh;
            background-color: rgba(255, 255, 255, 0.95);
            justify-content: center;
            align-items: center;
            overflow: hidden;
        }

        .image-modal.open {
            display: flex;
        }

        .image-modal .modal-content {
            max-width: 90%;
            max-height: 90%;
            transition: transform 0.2s ease;
            cursor: zoom-out;
            border-radius: 12px;
            box-shadow: 0 0 30px rgba(0, 0, 0, 0.15);
        }

        .close-modal {
            position: absolute;
            top: 20px;
            right: 30px;
            color: var(--accent);
            font-size: 40px;
            font-weight: bold;
            cursor: pointer;
            background: rgba(255, 255, 255, 0.9);
            width: 50px;
            height: 50px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
        }

        .image-modal .close-modal:hover {
            color: var(--accent-hover);
            transform: rotate(90deg);
        }

        @keyframes spin {
            0% {
                transform: rotate(0deg);
            }

            100% {
                transform: rotate(360deg);
            }
        }

        /* 响应式设计 */
        @media (max-width: 992px) {
            .sidebar {
                position: fixed;
                left: -100%;
                z-index: 1000;
                height: 100%;
                background-color: rgba(255, 255, 255, 0.95);
                backdrop-filter: blur(5px);
            }

            .sidebar.open {
                left: 0;
            }

            .menu-toggle {
                display: block;
                position: fixed;
                top: 20px;
                left: 20px;
                z-index: 900;
                background-color: white;
                border-radius: 50%;
                width: 45px;
                height: 45px;
                display: flex;
                align-items: center;
                justify-content: center;
                box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
                color: var(--accent);
            }

            .main-content {
                padding: 20px;
                padding-top: 80px;
            }
        }

        .text-muted {
            color: var(--text-secondary) !important;
        }

        /* 切换按钮样式 - 优化后 */
        .toggle-buttons {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
        }

        .toggle-file-btn,
        .toggle-db-btn,
        .toggle-notify-btn,
        .toggle-project-btn {
            padding: 8px 16px;
            background-color: white;
            border: 1px solid var(--border);
            border-radius: 20px;
            color: var(--text-secondary);
            cursor: pointer;
            font-weight: 500;
            transition: var(--transition);
            margin-right: 8px;
        }

        .toggle-file-btn:hover,
        .toggle-db-btn:hover,
        .toggle-notify-btn:hover,
        .toggle-project-btn:hover {
            background-color: rgba(74, 140, 156, 0.15);
            color: var(--accent);
        }

        .toggle-file-btn.active,
        .toggle-db-btn.active,
        .toggle-notify-btn.active,
        .toggle-project-btn.active {
            background-color: var(--accent);
            color: white;
            border-color: var(--accent);
        }

        /* 小清新装饰元素 */
        .leaf-decoration {
            position: absolute;
            width: 100px;
            height: 100px;
            background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M50,15 C65,5 85,10 85,30 C85,50 65,70 50,85 C35,70 15,50 15,30 C15,10 35,5 50,15 Z" fill="%238dc8a9" opacity="0.4"/></svg>');
            background-size: contain;
            background-repeat: no-repeat;
            z-index: -1;
            opacity: 0.4;
        }

        .leaf-1 {
            top: 10%;
            left: 5%;
            transform: rotate(15deg);
        }

        .leaf-2 {
            top: 40%;
            right: 5%;
            transform: rotate(-25deg);
        }

        .leaf-3 {
            bottom: 10%;
            left: 20%;
            transform: rotate(45deg);
        }
    </style>
</head>

<body>
    <!-- 侧边栏菜单 -->
    <aside class="sidebar" id="sidebar">
        <div class="sidebar-header">
            <div class="logo">
                <i class="fas fa-robot"></i>
                <span>AutoAPI</span>
            </div>
        </div>
        <ul class="menu">
            <li class="menu-item">
                <a href="#" class="menu-link active" data-module="home">
                    <i class="fas fa-home"></i>
                    <span>框架概览</span>
                </a>
            </li>

            <li class="menu-item">
                <a href="#" class="menu-link" data-module="structure">
                    <i class="fas fa-folder-tree"></i>
                    <span>项目结构</span>
                </a>
            </li>

            <li class="menu-item">
                <a href="#" class="menu-link" data-module="quick-start">
                    <i class="fas fa-bolt"></i>
                    <span>快速使用</span>
                </a>
            </li>

            <li class="menu-item">
                <a href="#" class="menu-link submenu-toggle">
                    <i class="fas fa-cog"></i>
                    <span>配置管理</span>
                    <i class="fas fa-chevron-right menu-arrow"></i>
                </a>
                <ul class="submenu">
                    <li><a href="#" class="submenu-link" data-module="config-basic">基本配置</a></li>
                    <li><a href="#" class="submenu-link" data-module="config-database">数据库配置</a></li>
                    <li><a href="#" class="submenu-link" data-module="config-notification">通知配置</a></li>
                </ul>
            </li>

            <li class="menu-item">
                <a href="#" class="menu-link submenu-toggle">
                    <i class="fas fa-database"></i>
                    <span>用例数据</span>
                    <i class="fas fa-chevron-right menu-arrow"></i>
                </a>
                <ul class="submenu">
                    <li><a href="#" class="submenu-link" data-module="test-data-params">参数说明</a></li>
                    <li><a href="#" class="submenu-link" data-module="test-data-intro">完整示例</a></li>
                    <li><a href="#" class="submenu-link" data-module="test-data-example">数据实例</a></li>
                </ul>
            </li>

            <li class="menu-item">
                <a href="#" class="menu-link" data-module="auth">
                    <i class="fas fa-lock"></i>
                    <span>授权与认证</span>
                </a>
            </li>

            <li class="menu-item">
                <a href="#" class="menu-link" data-module="hooks">
                    <i class="fas fa-code"></i>
                    <span>Hooks与Regular</span>
                </a>
            </li>

            <li class="menu-item">
                <a href="#" class="menu-link" data-module="testcase-gen">
                    <i class="fas fa-cogs"></i>
                    <span>用例生成</span>
                </a>
            </li>

            <li class="menu-item">
                <a href="#" class="menu-link" data-module="reports">
                    <i class="fas fa-chart-bar"></i>
                    <span>测试报告</span>
                </a>
            </li>

            <li class="menu-item">
                <a href="#" class="menu-link" data-module="faq">
                    <i class="fas fa-question-circle"></i>
                    <span>常见问题</span>
                </a>
            </li>
        </ul>
    </aside>

    <button class="menu-toggle" id="menuToggle">
        <i class="fas fa-bars"></i>
    </button>

    <!-- 主内容区域 -->
    <main class="main-content" id="mainContent">
        <div class="content-loading">
            <i class="fas fa-spinner fa-spin"></i>
            <p>加载内容中...</p>
        </div>
    </main>

    <!-- 所有模块内容（隐藏） -->
    <div id="module-content" style="display: none;">
        <!-- 框架概览 -->
        <div id="home-template">
            <div class="content-header">
                <h1><i class="fas fa-robot"></i> AutoAPI 自动化测试框架</h1>
                <p>基于Python的REST API自动化测试解决方案</p>
            </div>
            <!-- 项目简介 -->
            <div class="content-card">
                <h2><i class="fas fa-book-open"></i> 项目简介</h2>
                <p>AutoAPI 是一个基于 Python 的 REST API 自动化测试框架，支持 HTTP/HTTPS 协议接口测试，提供完善的测试数据管理、用例执行和报告生成功能。</p>
                <p>本框架二改于wu-clan接口自动化框架，原作者Gitee地址：<a href="https://github.com/wu-clan/httpfpt"
                        target="_blank">原作者wu-clanGitee地址，点击查看</a></p>
            </div>
            <!-- 接口文档 -->
            <div class="content-card">
                <h2><i class="fas fa-book-open"></i> 接口练习</h2>
                <P>本框架提供了一个练习接口文档，供大家练习使用，文档地址：<a href="autoapi\data\files\客达天下API文档.pdf"
                        target="_blank">客达天下API文档-w.pdf</a></P>
                <p>练习接口文档包含了多个接口的详细说明和示例，您可以根据文档中的说明进行接口测试练习。</p>
            </div>
            <!-- 核心优势 -->
            <div class="content-card">
                <h2><i class="fas fa-rocket"></i> 核心优势</h2>
                <div class="feature-grid">
                    <div class="feature-card">
                        <h3><i class="fas fa-layer-group"></i> 多项目管理</h3>
                        <p>支持多项目分级管理，自由切换不同项目，项目间测试环境完全隔离。</p>
                    </div>
                    <div class="feature-card">
                        <h3><i class="fas fa-cogs"></i> 动态环境配置</h3>
                        <p>自动识别和应用当前请求所需的基础环境配置，支持自定义环境变量。</p>
                    </div>
                    <div class="feature-card">
                        <h3><i class="fas fa-bolt"></i> 多元化断言</h3>
                        <p>支持 json 断言、sql 断言、json-schema 断言、正则断言、原生 python assert 断言。</p>
                    </div>
                    <div class="feature-card">
                        <h3><i class="fas fa-file-alt"></i> 多格式支持</h3>
                        <p>兼容 yaml / json 两种文件格式编写测试数据，满足不同团队需求。</p>
                    </div>
                    <div class="feature-card">
                        <h3><i class="fas fa-file-alt"></i> 多种通知渠道</h3>
                        <p>支持钉钉/飞书/邮箱/企微测试报告通知，测试运行完成后将自动发送测试结果，满足不同公司需求。</p>
                    </div>
                </div>
            </div>
            <!-- 功能列表 -->
            <div class="content-card">
                <h2><i class="fas fa-star"></i> 核心功能与优势</h2>
                <ul>
                    <li><span class="badge badge-success">✅</span> 多项目分级，自由切换，互不干扰</li>
                    <li><span class="badge badge-success">✅</span> 测试数据隔离，自动解析与验证</li>
                    <li><span class="badge badge-success">✅</span> 测试数据错误定位（参数错误，重复测试用例ID...）</li>
                    <li><span class="badge badge-success">✅</span> 多环境自定义配置，不同用例可以选择不同的运行环境</li>
                    <li><span class="badge badge-success">✅</span> 动态环境配置，自动识别和应用当前请求所需的基础环境配置</li>
                    <li><span class="badge badge-success">✅</span> 动态参数化，可通过全局变量，局部变量，缓存变量，关联变量等方式进行参数化</li>
                    <li><span class="badge badge-success">✅</span> 数据依赖，支持接口返回数据共享，轻松实现接口依赖</li>
                    <li><span class="badge badge-success">✅</span> 钩子函数，支持调用自定义钩子函数，实现更多的自定义功能</li>
                    <li><span class="badge badge-success">✅</span> 日志记录，自动记录测试过程中的请求数据日志</li>
                    <li><span class="badge badge-success">✅</span> 多元化断言，json 断言，sql 断言，json-schema 断言，正则断言，原生
                        python
                        assert 断言</li>
                    <li><span class="badge badge-success">✅</span> 兼容 yaml / json 两种文件格式编写测试数据</li>
                    <li><span class="badge badge-success">✅</span> 测试用例自动生成，可以根据测试数据文件自动生成测试用例</li>
                    <li><span class="badge badge-success">✅</span> 自动测试报告，html, allure</li>
                    <li><span class="badge badge-success">✅</span> 自动测试结果通知，飞书，钉钉，企业微信，邮箱</li>
                </ul>
            </div>
        </div>

        <!-- 项目结构 -->
        <div id="structure-template">
            <div class="content-header">
                <h1><i class="fas fa-folder-tree"></i> 项目结构</h1>
                <p>框架目录结构及多项目介绍</p>
            </div>
            <div class="toggle-buttons">
                <button class="toggle-project-btn active" data-project="directory">目录结构</button>
                <button class="toggle-project-btn" data-project="projects">多项目概念</button>
            </div>
            <div class="content-card">
                <!-- 目录结构 -->
                <div class="directory-structure">
                    <h2><i class="fas fa-sitemap"></i> 目录结构</h2>
                    <div class="code-block">
                        <pre><code>autoapi
├─ common ·················· 公共方法
├─ core ···················· 配置
├─ data ···················· 测试数据
├─ db ······················ 数据库
├─ enums ··················· 枚举类
├─ schemas ················· 数据架构
├─ log ····················· 日志
├─ report ·················· 测试报告
├─ templates ··············· 数据模板
├─ testcases ··············· 测试项目和用例
├─ utils ··················· 工具包
├─ cli.py ·················· CLI 程序
├─ conftest.py ············· pytest 配置
├─ pytest.ini ·············· pytest 运行配置
└─ run.py ·················· 主程序运行入口</code></pre>
                    </div>

                    <h3>主要模块说明</h3>
                    <ul>
                        <li><strong>core/</strong> - 框架核心配置，包括环境配置、全局设置等</li>
                        <li><strong>data/</strong> - 测试数据存储，支持多项目多模块组织</li>
                        <li><strong>testcases/</strong> - 自动生成的测试用例</li>
                        <li><strong>report/</strong> - 生成的测试报告（HTML、Allure等）</li>
                        <li><strong>run.py</strong> - 主程序入口，启动测试执行</li>
                    </ul>
                </div>
                <!-- 多项目概念 -->
                <div class="projects-concept" style="display: none;">
                    <h2><i class="fas fa-project-diagram"></i> 多项目概念</h2>
                    <p>多项目是为了能够将多个项目同时应用在此套框架中，并且需要隔离不同项目之间的测试用例和测试数据，而设计的一种运行模式</p>

                    <div class="code-block">
                        <pre><code>├─autoapi
 | |
 | ├─data
 | | └─test_data
 | |   └─project1
 | |   └─project2
 | |     └─module1
 | |     └─module2
 | |
 | ├─testcases # 自动生成
 | | └─project1
 | | └─project2
 | |   └─module1
 | |   └─module2</code></pre>
                    </div>

                    <h3>多项目优势</h3>
                    <ul>
                        <li><strong>项目隔离</strong> - 不同项目的测试数据和测试用例完全隔离，互不影响</li>
                        <li><strong>环境独立</strong> - 每个项目可以有自己的环境配置，如开发环境、测试环境、预发布环境等</li>
                        <li><strong>模块化管理</strong> - 支持在项目下创建模块，更好地组织测试用例</li>
                        <li><strong>灵活切换</strong> - 可以轻松切换不同项目进行测试执行</li>
                    </ul>
                </div>
            </div>
        </div>

        <!-- 快速使用 -->
        <div id="quick-start-template">
            <div class="content-header">
                <h1><i class="fas fa-bolt"></i> 快速使用</h1>
                <p>快速上手AutoAPI框架</p>
            </div>

            <div class="content-card">
                <h2><i class="fas fa-laptop-code"></i> 环境准备</h2>
                <p>在开始使用AutoAPI之前，请确保您的环境满足以下要求：</p>

                <ol>
                    <li><strong>Python 3.8+</strong> - 框架基于Python开发，需要Python 3.8或更高版本</li>
                    <li><strong>依赖安装</strong> - 安装项目所需依赖</li>
                </ol>

                <div class="code-block">
                    <pre><code># 安装依赖
pip install pdm    # 保证团队使用的依赖版本一致
pip install -r requirements.txt</code></pre>
                </div>
            </div>

            <div class="content-card">
                <h2><i class="fas fa-cogs"></i> 基本配置</h2>
                <p>在运行测试之前，需要进行基本配置：</p>
                <p>文件位置：autoapi\core\conf.toml</p>

                <div class="code-block">
                    <pre><code># 测试项目
[project]
name = 'test_kdtx'    # kdtx下所有模块
# name = 'test_kdtx/test_course'    # 只测试kdtx项目中course模块</code></pre>
                </div>
            </div>

            <div class="content-card">
                <h2><i class="fas fa-play-circle"></i> 运行测试</h2>
                <p>配置完成后，即可运行测试：</p>

                <div class="code-block">
                    <pre><code># 运行所有测试
python run.py

# 运行指定项目
python run.py --project test_kdtx

# 运行指定模块
python run.py --project test_kdtx/test_course</code></pre>
                </div>
            </div>
        </div>

        <!-- 基本配置 -->
        <div id="config-basic-template">
            <div class="content-header">
                <h1><i class="fas fa-cog"></i> 基本配置</h1>
                <p>框架基础配置说明</p>
                <p>文件位置：autoapi\core\conf.toml</p>
            </div>
            <!-- 项目配置 -->
            <div class="content-card">
                <h2><i class="fas fa-sliders-h"></i> 项目配置</h2>
                <div class="code-block">
                    <pre><code># 测试项目
[project]
name = 'test_kdtx'    # kdtx下所有模块
# name = 'test_kdtx/test_course'    # 只测试kdtx项目中course模块</code></pre>
                </div>
            </div>
            <!-- 测试报告配置 -->
            <div class="content-card">
                <h2><i class="fas fa-file-alt"></i> 测试报告配置</h2>
                <div class="code-block">
                    <pre><code># 测试报告
[report]
title： 测试报告标题
tester_name： 测试人员名称</code></pre>
                </div>

                <h3>报告类型配置</h3>
                <ul>
                    <li><strong>HTML报告</strong> - 默认开启，无需额外配置</li>
                    <li><strong>Allure报告</strong> - 需要安装Allure命令行工具，默认开启</li>
                    <li><strong>YAML报告</strong> - 用于存储测试结果数据，默认开启</li>
                </ul>
            </div>
            <!-- 请求配置 -->
            <div class="content-card">
                <h2><i class="fas fa-file-alt"></i> 请求配置</h2>

                <div class="code-block">
                    <pre><code># 请求发送
[request]
global_env = '' # 全局环境名（如 dev / test / pre）
timeout = 10 # 请求超时时间，单位秒
verify = false # 是否开启 SSL 证书验证
redirects = true # 是否允许重定向
proxies.http = '' # HTTP 代理
proxies.https = '' # HTTPS 代理
retry = 3 # 请求失败重试次数</code></pre>

                </div>

                <h3>配置说明</h3>
                <ul>
                    <li><strong>global_env</strong> - 当前使用的环境标识，会影响 base_url 加载</li>
                    <li><strong>timeout</strong> - 请求最大等待时间，单位为秒</li>
                    <li><strong>verify</strong> - 是否验证 SSL 证书，关闭可用于跳过自签名证书</li>
                    <li><strong>redirects</strong> - 是否允许请求重定向</li>
                    <li><strong>proxies.http / https</strong> - 请求代理设置</li>
                    <li><strong>retry</strong> - 网络失败时最多重试次数</li>
                </ul>
            </div>
        </div>

        <!-- 数据库配置 -->
        <div id="config-database-template">
            <div class="content-header">
                <h1><i class="fas fa-database"></i> 数据库配置</h1>
                <p>包括 MySQL 和 Redis 的数据库连接配置</p>
                <p>文件位置：<code>autoapi\core\conf.toml</code></p>
            </div>
            <div class="toggle-buttons">
                <button class="toggle-db-btn active" data-db="mysql">MySQL 配置</button>
                <button class="toggle-db-btn" data-db="redis">Redis 配置</button>
            </div>
            <div class="content-card">
                <!-- MySQL 配置块 -->
                <div class="db-mysql">
                    <h2><i class="fas fa-server"></i> MySQL数据库配置</h2>
                    <div class="code-block">
                        <pre><code># mysql 数据库
[mysql]
enable = false # 数据库连接开关，设置为 false 禁用数据库连接
default = "main_db" # 设置默认数据库

# mysql 主数据库
[mysql.main_db]
host = '47.116.123.251'
port = 3306
user = 'shopuser'
password = '123456'
database = 'shopdb'
charset = 'utf8mb4'
cursorclass = 'pymysql.cursors.DictCursor'

# mysql 用户数据库
[mysql.user_db]
host = '47.116.123.251'
port = 3306
user = 'shopuser'
password = '123456'
database = 'shopdb'
charset = 'utf8mb4'
cursorclass = 'pymysql.cursors.DictCursor'

# mysql 订单数据库
[mysql.order_db]
host = '47.116.123.251'
port = 3306
user = 'shopuser'
password = '123456'
database = 'shopdb'
charset = 'utf8mb4'
cursorclass = 'pymysql.cursors.DictCursor'</code></pre>
                    </div>

                    <h3>配置说明</h3>
                    <ul>
                        <li><strong>host</strong> - 数据库服务器地址</li>
                        <li><strong>port</strong> - 端口号</li>
                        <li><strong>user</strong> - 用户名</li>
                        <li><strong>password</strong> - 密码</li>
                        <li><strong>database</strong> - 数据库名称</li>
                        <li><strong>charset</strong> - 字符集编码（建议 utf8mb4）</li>
                    </ul>
                </div>

                <!-- Redis 配置块 -->
                <div class="db-redis" style="display: none;">
                    <h2><i class="fas fa-server"></i> Redis数据库配置</h2>
                    <div class="code-block">
                        <pre><code># redis 数据库
[redis]
host = '127.0.0.1' # 主机 IP
port = 6379 # 端口号
password = '' # 密码
database = 0 # 数据库编号(0-16)
timeout = 10 # 超时时间</code></pre>
                    </div>

                    <h3>配置说明</h3>
                    <ul>
                        <li><strong>host</strong> - 数据库服务器地址</li>
                        <li><strong>port</strong> - 端口号</li>
                        <li><strong>password</strong> - 密码</li>
                        <li><strong>database</strong> - 选择的 Redis DB 编号</li>
                        <li><strong>timeout</strong> - 连接超时时间（单位：秒）</li>
                    </ul>
                </div>
            </div>
        </div>

        <!-- 通知配置 -->
        <div id="config-notification-template">
            <div class="content-header">
                <h1><i class="fas fa-database"></i> 通知配置</h1>
                <p>包括邮件、钉钉、飞书、企微平台测试结果自动化通知</p>
                <p>文件位置：autoapi\core\conf.toml</p>

            </div>
            <div class="toggle-buttons">
                <button class="toggle-notify-btn active" data-type="email">邮件通知</button>
                <button class="toggle-notify-btn" data-type="dingding">钉钉通知</button>
                <button class="toggle-notify-btn" data-type="feishu">飞书通知</button>
                <button class="toggle-notify-btn" data-type="wechat">企微通知</button>
            </div>
            <div class="content-card">


                <!-- 邮件配置块 -->
                <div class="notify-block notify-email">
                    <h2><i class="fas fa-envelope"></i> 邮件通知设置</h2>
                    <div class="code-block code-yaml">
                        <pre><code># 邮件通知
[email]
host = 'smtp.qq.com' # 邮件服务器
port = 465 # 端口号
user = '2349874@qq.com' # 邮箱
password = 'fvdtayrohzubifa' # 服务密码，非邮箱密码
receiver = '2349874@qq.com' # 收件人，若多人，需逗号隔开
ssl = true # 是否 SSL 验证
send = false # 是否发送邮件测试报告，默认关闭</code></pre>
                    </div>

                    <h3>配置说明</h3>
                    <ul>
                        <li><strong>host</strong> - SMTP服务器地址</li>
                        <li><strong>port</strong> - SMTP服务器端口</li>
                        <li><strong>user</strong> - 发件人邮箱地址</li>
                        <li><strong>password</strong> - 邮箱服务密码（注意不是邮箱登录密码）</li>
                        <li><strong>receiver</strong> - 收件人邮箱，多个用逗号分隔</li>
                        <li><strong>ssl</strong> - 是否启用SSL加密</li>
                        <li><strong>send</strong> - 是否发送邮件报告，设置为true开启</li>
                    </ul>
                </div>

                <!-- 钉钉配置块 -->
                <div class="notify-block notify-dingding" style="display: none;">
                    <h2><i class="fas fa-robot"></i> 钉钉通知配置</h2>
                    <div class="code-block">
                        <pre><code># 钉钉
[dingding]
webhook = '' # 钉钉机器人 Webhook 地址
proxies.http = '' # HTTP 代理
proxies.https = '' # HTTPS 代理
send = false # 是否启用钉钉通知</code></pre>
                    </div>
                    <h3>配置说明</h3>
                    <ul>
                        <li><strong>webhook</strong> - 钉钉自定义机器人 webhook 地址</li>
                        <li><strong>proxies.http / https</strong> - 网络代理（如有）</li>
                        <li><strong>send</strong> - 是否启用钉钉通知，true 为启用</li>
                    </ul>
                    <div class="alert alert-warning">
                        <i class="fas fa-exclamation-triangle"></i>
                        <div>
                            <strong>注意</strong>
                            <p></p>
                            <p>我们公司使用的是飞书，钉钉通知的代码未进行优化</p>
                            <p class="small text-muted mb-0">代码位置：autoapi\utils\send_report\dingding.py</p>
                        </div>
                    </div>
                </div>

                <!-- 飞书配置块 -->
                <div class="notify-block notify-feishu" style="display: none;">
                    <h2><i class="fas fa-comment-dots"></i> 飞书通知配置</h2>
                    <div class="code-block">
                        <pre><code># 飞书
[feishu]
webhook = '' # 飞书机器人 webhook 地址
proxies.http = '' # 请求 http 代理
proxies.https = '' # 请求 https 代理
mention_user = '' # 需要艾特的人，多人请用逗号隔开
send = false # 是否发送飞书测试报告，默认关闭</code></pre>
                    </div>
                    <h3>配置说明</h3>
                    <ul>
                        <li><strong>webhook</strong> - 飞书机器人的Webhook地址</li>
                        <li><strong>proxies</strong> - 代理设置（如果需要通过代理访问飞书）</li>
                        <li><strong>mention_user</strong> - 需要@的用户ID，多个用逗号分隔</li>
                        <li><strong>send</strong> - 是否发送飞书通知，设置为true开启</li>
                    </ul>
                    <h3>UID获取方法</h3>
                    <p>连续点击“关于飞书” 5 次</p>
                    <div class="image-container">
                        <img src="autoapi\data\images\feishu-uid.png" alt="飞书UID获取">
                        <p>飞书UID获取方法示意图</p>
                    </div>
                </div>

                <!-- 企微配置块 -->
                <div class="notify-block notify-wechat" style="display: none;">
                    <h2><i class="fas fa-robot"></i> 企微通知配置</h2>
                    <div class="code-block">
                        <pre><code># 企业微信
[wechat]
webhook = '' # 企业微信机器人 Webhook
proxies.http = '' # HTTP 代理
proxies.https = '' # HTTPS 代理
send = false # 是否启用企业微信通知</code></pre>

                    </div>

                    <h3>配置说明</h3>
                    <ul>
                        <li><strong>webhook</strong> - 企业微信机器人 webhook 地址</li>
                        <li><strong>proxies.http / https</strong> - 网络代理（如无特殊需求可为空）</li>
                        <li><strong>send</strong> - 是否启用企业微信通知，true 为启用</li>
                    </ul>
                    <div class="alert alert-warning">
                        <i class="fas fa-exclamation-triangle"></i>
                        <div>
                            <strong>注意</strong>
                            <p></p>
                            <p>我们公司使用的是飞书，企微通知的代码未进行优化</p>
                            <p class="small text-muted mb-0">
                                代码位置：autoapi\utils\send_report\wechat.py
                            </p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <!-- 参数说明 -->
        <div id="test-data-params-template">
            <div class="content-header">
                <h1><i class="fas fa-list"></i> 参数说明</h1>
                <p>测试数据文件参数详解</p>
            </div>
            <div class="toggle-buttons">
                <button class="toggle-file-btn active" data-type="all_params">全部参数</button>
                <button class="toggle-file-btn" data-type="special_params">特殊参数</button>
            </div>

            <!-- 全部参数 -->
            <div class="table-container all_params">
                <div class="content-card">
                    <h2><i class="fas fa-table"></i> 全部参数表</h2>
                    <table class="styled-table">
                        <thead>
                            <tr>
                                <th>参数</th>
                                <th>类型</th>
                                <th>必填</th>
                                <th>说明</th>
                            </tr>
                        </thead>
                        <tbody>
                            <!-- config -->
                            <tr class="foldable parent">
                                <td>config</td>
                                <td>dict</td>
                                <td>Y</td>
                                <td>当前数据文件中所有测试用例的统一配置</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;allure</td>
                                <td>dict</td>
                                <td>Y</td>
                                <td>allure 测试报告配置</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;epic</td>
                                <td>str</td>
                                <td>Y</td>
                                <td>epic 标题</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;feature</td>
                                <td>str</td>
                                <td>Y</td>
                                <td>feature 标题</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;story</td>
                                <td>str</td>
                                <td>Y</td>
                                <td>story 标题</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;feature</td>
                                <td>str</td>
                                <td>Y</td>
                                <td>功能名称</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;story</td>
                                <td>str</td>
                                <td>Y</td>
                                <td>用户故事</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;severity</td>
                                <td>str</td>
                                <td>N</td>
                                <td>严重等级</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;request</td>
                                <td>dict</td>
                                <td>Y</td>
                                <td>请求默认配置</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;env</td>
                                <td>str</td>
                                <td>Y</td>
                                <td>环境配置文件路径</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;headers</td>
                                <td>dict</td>
                                <td>N</td>
                                <td>默认请求头</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;timeout</td>
                                <td>int</td>
                                <td>N</td>
                                <td>超时时间（秒）</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;verify</td>
                                <td>bool</td>
                                <td>N</td>
                                <td>是否校验证书</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;redirects</td>
                                <td>bool</td>
                                <td>N</td>
                                <td>是否允许重定向</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;proxies</td>
                                <td>dict</td>
                                <td>N</td>
                                <td>HTTP/HTTPS 代理配置</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;http</td>
                                <td>str</td>
                                <td>N</td>
                                <td>HTTP代理</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;https</td>
                                <td>str</td>
                                <td>N</td>
                                <td>HTTPS代理</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;retry</td>
                                <td>int</td>
                                <td>N</td>
                                <td>请求失败重试次数</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;module</td>
                                <td>str</td>
                                <td>Y</td>
                                <td>模块名称</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;mark</td>
                                <td>str</td>
                                <td>N</td>
                                <td>标签标记</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;is_run</td>
                                <td>bool</td>
                                <td>N</td>
                                <td>是否执行</td>
                            </tr>

                            <!-- test_steps -->
                            <tr class="foldable parent">
                                <td>test_steps</td>
                                <td>list</td>
                                <td>Y</td>
                                <td>测试步骤列表</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;name</td>
                                <td>str</td>
                                <td>Y</td>
                                <td>测试用例名称</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;case_id</td>
                                <td>str</td>
                                <td>Y</td>
                                <td>用例 ID</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;description</td>
                                <td>str</td>
                                <td>N</td>
                                <td>用例描述</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;is_run</td>
                                <td>bool</td>
                                <td>N</td>
                                <td>是否执行该步骤</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;mark</td>
                                <td>str</td>
                                <td>N</td>
                                <td>标记</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;retry</td>
                                <td>int</td>
                                <td>N</td>
                                <td>重试次数</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;request</td>
                                <td>dict</td>
                                <td>Y</td>
                                <td>请求体</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;method</td>
                                <td>str</td>
                                <td>Y</td>
                                <td>请求方法</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;url</td>
                                <td>str</td>
                                <td>Y</td>
                                <td>请求地址</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;params</td>
                                <td>dict</td>
                                <td>N</td>
                                <td>请求参数</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;headers</td>
                                <td>dict</td>
                                <td>N</td>
                                <td>请求头</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;cookies</td>
                                <td>dict</td>
                                <td>N</td>
                                <td>请求 Cookies</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;body_type</td>
                                <td>str</td>
                                <td>N</td>
                                <td>请求体类型</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;body</td>
                                <td>dict</td>
                                <td>N</td>
                                <td>请求体内容</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;files</td>
                                <td>dict</td>
                                <td>N</td>
                                <td>上传文件</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;setup</td>
                                <td>list</td>
                                <td>N</td>
                                <td>前置操作</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;- testcase</td>
                                <td>str</td>
                                <td>N</td>
                                <td>调用用例</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;- sql</td>
                                <td>str</td>
                                <td>N</td>
                                <td>SQL操作</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;- hook</td>
                                <td>str</td>
                                <td>N</td>
                                <td>钩子函数</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;- wait_time</td>
                                <td>int</td>
                                <td>N</td>
                                <td>等待时间</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;teardown</td>
                                <td>list</td>
                                <td>N</td>
                                <td>后置操作</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;- sql</td>
                                <td>str</td>
                                <td>N</td>
                                <td>SQL操作</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;- hook</td>
                                <td>str</td>
                                <td>N</td>
                                <td>钩子函数</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;- extract</td>
                                <td>dict</td>
                                <td>N</td>
                                <td>提取参数</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;- assert</td>
                                <td>dict</td>
                                <td>N</td>
                                <td>断言条件</td>
                            </tr>
                            <tr>
                                <td>&nbsp;&nbsp;&nbsp;&nbsp;- wait_time</td>
                                <td>int</td>
                                <td>N</td>
                                <td>等待时间</td>
                            </tr>
                        </tbody>
                    </table>

                </div>
                <div class="alert alert-info">
                    <i class="fas fa-exclamation-triangle"></i>
                    <div>
                        <strong>注意</strong>
                        <p></p>
                        <p>所有带 "Y / Y" 的字段表示：如果其父级字段设置了该项，则此项必须填写。</p>
                        <p>YAML 文件的格式需要严格对齐缩进层级，建议使用 2 空格缩进。</p>
                    </div>
                </div>
            </div>
            <!-- 特殊参数说明 -->
            <div class="table-container special_params" style="display: none;">
                <div class="content-card">
                    <h2><i class="fas fa-table"></i> 特殊参数说明</h2>

                    <div class="content-card-two">
                        <h3>is_run</h3>
                        <p>config 和 test_steps 中的 <strong>is_run</strong> 参数有多种实现方式，决定是否执行测试用例：</p>

                        <ul>
                            <li><strong>None 值</strong> - 默认执行
                                <div class="code-block">
                                    <pre><code>is_run:  # None</code></pre>
                                </div>
                            </li>
                            <li><strong>bool 类型</strong> - True/False，是否跳过执行
                                <div class="code-block">
                                    <pre><code>is_run: True  # bool</code></pre>
                                </div>
                            </li>
                            <li><strong>dict 类型 - skip + reason</strong> - 自定义跳过执行原因
                                <div class="code-block">
                                    <pre><code>is_run:
skip: True
reason: 跳过执行原因</code></pre>
                                </div>
                            </li>
                            <li><strong>dict 类型 - skip_if + reason</strong> - 条件为真时跳过执行
                                <div class="code-block">
                                    <pre><code>is_run:
skip_if:
    - ${timeout} == None
    - python 表达式
reason: 跳过执行原因</code></pre>
                                </div>
                            </li>
                        </ul>
                    </div>

                    <div class="content-card-two">
                        <h3><i class="fas fa-layer-group"></i> 请求前后置</h3>
                        <p>setup / teardown 数据定义中，一条数据相当于一个动作，支持多个动作按顺序执行</p>

                        <div class="code-block">
                            <pre><code>setup:
  - hook: xxx
  - testcase: xxx
  - hook: xxx
  - sql: xxx
teardown:
  - sql: xxx
  - extract: xxx
  - sql: xxx
  - assert: xxx</code></pre>
                        </div>
                    </div>

                    <div class="content-card-two">
                        <h3><i class="fas fa-vial"></i> testcase 参数</h3>
                        <p>setup 中的 <strong>testcase</strong> 支持三种功能</p>

                        <ol>
                            <li>执行关联测试用例
                                <div class="code-block">
                                    <pre><code>- testcase: search_course_01</code></pre>
                                </div>
                            </li>
                            <li>关联测试用例变量提取
                                <div class="code-block">
                                    <pre><code>- testcase:
    case_id: search_course_01
    response:
    - key: id
        jsonpath: $.json.rows[0].id</code></pre>
                                </div>
                            </li>
                            <li>更新关联测试用例请求数据
                                <div class="code-block">
                                    <pre><code>- testcase:
    case_id: search_course_01
    request:
    - value: 要更新的值
        jsonpath: $.json.rows[0].id</code></pre>
                                </div>
                            </li>
                        </ol>
                    </div>

                    <div class="content-card-two">
                        <h3><i class="fas fa-database"></i> sql 参数</h3>
                        <p>setup / teardown 中的 sql 参数支持两种功能</p>
                        <div class="code-block">
                            <pre><code>- sql:
    db: 使用的数据库连接，默认 default # str
    fetch: 提取单条 one / 多条 all，默认 one # str
    key: 变量 key # str
    type: 变量类型：env / global / cache # str
    sql: 执行 sql 查询 # str
    jsonpath: 值 value, jsonpath 表达式, 数据依赖 sql 查询结果 # str</code></pre>
                            <pre><code>
# 例：使用默认数据库
- sql: DELETE FROM users WHERE name LIKE 'test_%';

# 例：指定数据库
- sql:
    sql: DELETE FROM users WHERE name LIKE 'test_%';
    db: user_db # 指定使用user_db数据库

# 例：使用查询结果提取变量
- sql:
    db: user_db
    fetch: one
    key: user_id
    type: cache
    sql: SELECT id FROM users WHERE name LIKE 'sibro%';
    jsonpath: $.json.id</code></pre>
                        </div>


                    </div>

                    <div class="content-card-two">
                        <h3><i class="fas fa-share-alt"></i> extract 参数</h3>
                        <div class="code-block">
                            <pre><code>- extract:
    key: 变量 key # str
    type: 变量类型：env / global / cache # str
    jsonpath: 值 value, jsonpath 表达式, 数据依赖 response 数据集 # str</code></pre>
                            <pre><code># 例：
- extract:
    key: test_cache
    type: cache
    jsonpath: $.status_code</code></pre>
                        </div>

                    </div>

                    <div class="content-card-two">
                        <h3><i class="fas fa-check-circle"></i> assert 参数</h3>
                        <p>teardown 中的 <strong>assert</strong> 支持多种实现方式</p>

                        <ol>
                            <li>常规断言
                                <div class="code-block">
                                    <pre><code>- assert: assert 200 == pm.response.get('status_code')</code></pre>
                                </div>
                            </li>
                            <li>jsonpath 断言
                                <div class="code-block">
                                    <pre><code>- assert:
    check: 响应状态码
    jsonpath: $.json.code
    type: eq
    value: 200</code></pre>
                                </div>
                            </li>
                            <li>sql 断言
                                <div class="code-block">
                                    <pre><code>- assert:
    check:
    type: eq
    value: 8848
    sql: SELECT p.name FROM phone p WHERE material = '钛金';
    jsonpath: $.name</code></pre>
                                </div>
                            </li>
                            <li>jsonschema 断言
                                <div class="code-block">
                                    <pre><code>- assert:
    check: null
    type: jsonschema
    jsonschema:
    title: Product
    description: A product from Acme's catalog
    type: object
    properties:
        productId:
        type: integer
        description: The unique identifier for a product</code></pre>
                                </div>
                            </li>
                        </ol>
                    </div>
                </div>
            </div>
        </div>

        <!-- 完整示例 -->
        <div id="test-data-intro-template">
            <div class="content-header">
                <h1><i class="fas fa-database"></i> 完整示例</h1>
                <p>测试数据文件结构与基本概念</p>
            </div>

            <div class="content-card">
                <h2><i class="fas fa-file-code"></i> 数据文件结构</h2>
                <p>AutoAPI支持两种格式的测试数据文件：YAML和JSON。每个数据文件包含两部分：</p>
                <ol>
                    <li><strong>config</strong> - 配置文件级别的配置项，包括模块信息、环境配置等</li>
                    <li><strong>test_steps</strong> - 测试步骤列表，包含一个或多个测试用例</li>
                </ol>
                <div class="toggle-buttons">
                    <button class="toggle-file-btn active" data-type="yaml">YAML</button>
                    <button class="toggle-file-btn" data-type="json">JSON</button>
                </div>
                <div class="code-block code-yaml">
                    <h2><i class="fas fa-file-alt"></i> YAML</h2>
                    <pre><code>config:
  allure:
    epic:
    feature:
    story:
    severity:
  request:
    env:
    headers:
    timeout:
    verify:
    redirects:
    proxies:
      http:
      https:
    retry:
  module:
  mark:
  is_run:

test_steps:
  name:
  case_id:
  description:
  is_run:
  mark:
  retry:
  request:
    method:
    url:
    params:
    headers:
    cookies:
    body_type:
    body:
    files:
  setup: 
    - testcase:
    - sql:
    - hook:
    - wait_time:
  teardown:
    - sql:
    - hook:
    - extract:
    - assert:
    - wait_time:</code></pre>
                </div>
                <div class="code-block code-json" style="display: none;">
                    <h2><i class="fas fa-file-code"></i> JSON</h2>
                    <pre><code>{
  "config": {
    "allure": {
      "epic": null,
      "feature": null,
      "story": null,
      "severity": null
    },
    "request": {
      "env": null,
      "headers": {},
      "timeout": null,
      "verify": null,
      "redirects": null,
      "proxies": {
        "http": null,
        "https": null
      },
      "retry": null
    },
    "module": null,
    "mark": null,
    "is_run": null
  },
  "test_steps": {
    "name": null,
    "case_id": null,
    "description": null,
    "is_run": null,
    "mark": null,
    "retry": null,
    "request": {
      "method": null,
      "url": null,
      "params": {},
      "headers": {},
      "cookies": {},
      "body_type": null,
      "body": {},
      "files": {}
    },
    "setup": [
      {
        "testcase": null
      },
      {
        "sql": null
      },
      {
        "hook": null
      },
      {
        "wait_time": null
      }
    ],
    "teardown": [
      {
        "sql": null
      },
      {
        "hook": null
      },
      {
        "extract": null
      },
      {
        "assert": null
      },
      {
        "wait_time": null
      }
    ]
  }
}
</code></pre>
                </div>
            </div>
        </div>

        <!-- 用例实例 -->
        <div id="test-data-example-template">
            <div class="content-header">
                <h1><i class="fas fa-database"></i> 数据实例</h1>
                <p>测试数据文件示例</p>
            </div>

            <div class="content-card">
                <h2><i class="fas fa-file-code"></i> 测试用例数据</h2>
                <p>使用黑马客达天下项目合同管理作为实例，可在测试用例数据查看更多用例数据实例</p>
                <p class="small text-muted mb-0">代码位置：autoapi\data\test_data\test_kdtx\</p>
                <div class="toggle-buttons">
                    <button class="toggle-file-btn active" data-type="yaml">YAML</button>
                    <button class="toggle-file-btn" data-type="json">JSON</button>
                </div>
                <div class="code-block code-yaml">
                    <h2><i class="fas fa-file-alt"></i> YAML</h2>
                    <pre><code>config:
    module: 合同管理
    allure:
        epic: 测试平台接口
        feature: 新增合同
        story: 新增合同
    request:
        env: dev.env
        timeout: 10
        verify: false
        redirects: false

    test_steps:
    - name: 新增合同
        case_id: add_contract_01
        description: 测试新增合同
        is_run: true
        request:
        method: POST
        url: /api/contract
        headers:
            Content-Type: application/json;charset=UTF-8
        body_type: json
        body:
            name: zzzz
            phone: ${{get_phone()}}
            contractNo: FB00022
            subject: 6
            courseId: 99
            channel: 0
            activityId: 77
            fileName: ${demo()}
        setup:
        - hook: ${init_db_contract()}
        teardown:
        - hook: ${delete_contract()}
        - assert:
            check: 响应状态码
            jsonpath: $.json.code
            type: eq
            value: 200</code></pre>
                </div>


                <div class="code-block code-json" style="display: none;">
                    <h2><i class="fas fa-file-code"></i> JSON</h2>
                    <pre><code>{
    "config": {
        "module": "合同管理",
        "allure": {
        "epic": "测试平台接口",
        "feature": "新增合同",
        "story": "新增合同"
        },
        "request": {
        "env": "dev.env",
        "timeout": 10,
        "verify": false,
        "redirects": false
        }
    },
    "test_steps": [
        {
        "name": "新增合同",
        "case_id": "add_contract_01",
        "description": "测试新增合同",
        "is_run": true,
        "request": {
            "method": "POST",
            "url": "/api/contract",
            "headers": {
            "Content-Type": "application/json;charset=UTF-8"
            },
            "body_type": "json",
            "body": {
            "name": "zzzz",
            "phone": "${{get_phone()}}",
            "contractNo": "FB00022",
            "subject": 6,
            "courseId": 99,
            "channel": 0,
            "activityId": 77,
            "fileName": "${demo()}"
            }
        },
        "setup": [
            {
            "hook": "${init_db_contract()}"
            }
        ],
        "teardown": [
            {
            "hook": "${delete_contract()}"
            },
            {
            "assert": {
                "check": "响应状态码",
                "jsonpath": "$.json.code",
                "type": "eq",
                "value": 200
            }
            }
        ]
        }
    ]
    }</code></pre>
                </div>
            </div>
        </div>

        <!-- 授权与认证 -->
        <div id="auth-template">
            <div class="content-header">
                <h1><i class="fas fa-lock"></i> 授权与认证</h1>
                <p>接口认证配置说明</p>
            </div>

            <div class="content-card">
                <h2><i class="fas fa-cog"></i> 基础配置</h2>

                <h3>is_auth</h3>
                <p>是否开启自动授权：</p>
                <ul>
                    <li><strong>是</strong> - 在测试用例数据中的 headers 中可以不用填写 token</li>
                    <li><strong>否</strong> - 需要手动填写 token</li>
                </ul>

                <h3>auth_type</h3>
                <p>授权方式（固定值）：</p>
                <code>bearer_token</code>、<code>bearer_token_custom</code>、<code>header_cookie</code>
            </div>

            <div class="content-card">
                <h2><i class="fas fa-key"></i> Bearer Token</h2>
                <p>JWT 认证，自动添加至每个请求的请求头中。</p>
                <p>如果开启了自动授权，并在请求中手动配置了 token，那么该 token 会被当前自动获取的 token 覆盖。</p>

                <div class="code-block">
                    <pre><code>bearer_token:
    url: http://kdtx-test.itheima.net/api/login
    username: admin
    password: HM_2023_test
    code: "2"  
    uuid: "{{temp_param3}}"  
    headers:
        Content-Type: application/json
        User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
    token_key: $.token
    timeout: 100000
    temp_get_params:
        - enable: false
          name: temp_param1  
          url: https://api.example.com/get_code
          method: get
          params: 
              user_id: 123
          value: $.code
        - enable: false
          name: temp_param2 #
          url: https://api.example.com/get_phone_code
          method: post
          json: 
              user_id: 123
              phone: 15000000000
          value: $.data.phone_code
        - enable: true
          name: temp_param3  
          url: http://kdtx-test.itheima.net/api/captchaImage
          method: get
          value: $.uuid</code></pre>
                </div>

                <p><strong>说明：</strong></p>
                <ul>
                    <li><code>token_key</code>：使用 JSONPath 从响应中提取 token</li>
                    <li><code>timeout</code>：token 的缓存时间，非接口返回的 token 过期时间</li>
                    <li><code>temp_get_params</code>：登录前需要预先获取的参数</li>
                    <p class="small text-muted mb-0">
                        示例中是kdtx的登录接口信息，需要uuid，所以在temp_get_params中提前获取传递给**登录接口**使用，注意获取变量方式**两个花括号{{参数名}}**
                        temp_get_params支持多个参数获取，示例中验证码固定是2，如果是手机验证码就需要将temp_param2和temp_param3的enable都设置为true，将
                        code: 2
                        改成 code: {{temp_param2}}</p>
                </ul>
            </div>

            <div class="content-card">
                <h2><i class="fas fa-user-lock"></i> Bearer Token Custom</h2>
                <p>绕过自动登录流程，手动配置 token。</p>

                <div class="code-block">
                    <pre><code>bearer_token_custom:
    token: xxx
    timeout: 100000</code></pre>
                </div>

                <p><strong>适用场景：</strong></p>
                <ul>
                    <li>测试环境需要固定 token</li>
                    <li>研发提供了长期有效的 token</li>
                    <li>从外部手动获取 token 后直接配置</li>
                    <li>timeout: 自定义当前 token 超时时间，而非服务器 token 本身超时时间，token 会根据此时间进行缓存,
                        避免频繁请求
                    </li>
                </ul>
            </div>

            <div class="content-card">
                <h2><i class="fas fa-cookie-bite"></i> Cookie 认证</h2>
                <p>使用 Cookie 认证，自动添加至请求头。</p>

                <div class="code-block">
                    <pre><code>header_cookie:
    url: xxx
    username: xxx
    password: xxx
    headers:
        Content-Type: application/json
        User-Agent: Mozilla/5.0 ...
    timeout: 100000</code></pre>
                </div>
            </div>
        </div>

        <!-- Hooks与regular -->
        <div id="hooks-template">
            <div class="content-header">
                <h1><i class="fas fa-code"></i> Hooks与Regular</h1>
                <p>钩子函数与动态随机数使用指南</p>
            </div>

            <div class="content-card">
                <h2><i class="fas fa-hook"></i> 文件模块职责划分</h2>

                <div class="table-container">
                    <table class="styled-table">
                        <thead>
                            <tr>
                                <th>模块</th>
                                <th>文件名</th>
                                <th>主要职责</th>
                                <th>用法场景</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr>
                                <td>Hook 注册器与执行器</td>
                                <td>hooks.py</td>
                                <td>注册并执行测试中 hook 函数</td>
                                <td>YAML 中的 hook 表达式，如 ${hook_name()}</td>
                            </tr>
                            <tr>
                                <td>通用变量替换器</td>
                                <td>regular_control.py</td>
                                <td>支持 ${{xxx()}} 格式，用于请求参数中动态值生成</td>
                                <td>YAML 请求数据构造，如 mobile: "${{get_phone()}}"</td>
                            </tr>
                        </tbody>
                    </table>
                </div>

                <h3>Hook 表达式（格式：${func_name(...)}）</h3>
                <ul>
                    <li><strong>执行时机：</strong>框架中由 HookExecutor 处理，仅处理
                        setup、teardown、extract、validate 等字段
                    </li>
                    <li><strong>语法要求：</strong>函数必须用 @hook 装饰器注册，保存在 __registered_hooks__
                    </li>
                    <li><strong>典型用途：</strong>数据初始化、清理数据等</li>
                </ul>

                <h3>regular() 表达式（格式：${{func_name(...)}}）</h3>
                <ul>
                    <li><strong>执行时机：</strong>用于请求数据的正则变量替换阶段</li>
                    <li><strong>语法支持：</strong>支持无参/有参调用；支持类型标注（如 int:、float:）</li>
                    <li><strong>常用方法：</strong>get_phone()、get_email()、random_int(min, max)等
                    </li>
                </ul>
                <h3>Yaml hook()与regular()用法示例</h3>
                <div class="code-block">
                    <pre><code>test_steps:
  - name: 创建合同
    hook: ${init_db_contract()}                 # hook方式
    request:
      method: POST
      url: /api/contract
      json:
        phone: "${{get_phone()}}"               # regular方式
        name: "${{get_female_name()}}"          # regular方式
        id_number: "${{get_id_number()}}"       # regular方式
    teardown:
      - ${delete_contract("abc123")}            # hook方式
                </code></pre>
                </div>
            </div>
        </div>

        <!-- 用例生成 -->
        <div id="testcase-gen-template">
            <div class="content-header">
                <h1><i class="fas fa-cogs"></i> 用例生成</h1>
                <p>自动生成测试用例的方法</p>
            </div>

            <div class="content-card">
                <h2><i class="fas fa-file-code"></i> 用例生成方法</h2>

                <h3>1. 手动清空用例</h3>
                <p>使用 clean_testcases() 方法可以清空指定项目下的所有测试用例文件(只删除test_*.py文件)</p>

                <div class="code-block">
                    <pre><code># 命令行方式
python -m autoapi.cli testcase -g # 会先清空再重新生成用例

# Python代码调用
from autoapi.utils.clean_case import clean_testcases
clean_testcases() # 返回删除的文件数量</code></pre>

                </div>

                <h3>2. 手动生成用例</h3>
                <p>使用 auto_generate_testcases() 方法可以根据数据文件自动生成测试用例</p>

                <div class="code-block">
                    <pre><code># 仅生成新用例(不覆盖已有用例)
python -m autoapi.cli testcase -g

# 强制重新生成所有用例(覆盖已有用例)
python -m autoapi.cli testcase -g --rewrite</code></pre>

                </div>

                <h3>3. 运行时自动生成用例配置</h3>
                <p>在 autoapi/run.py 中可以通过以下参数控制运行时自动生成用例行为：</p>

                <div class="code-block">
                    <pre><code>run(
# 自动生成测试用例配置
testcase_generate=True, # 是否自动生成测试用例
testcase_re_generation=True, # 是否覆盖生成同名文件

# 其他运行参数...
)</code></pre>

                </div>
            </div>
        </div>

        <!-- 测试报告 -->
        <div id="reports-template">
            <div class="content-header">
                <h1><i class="fas fa-chart-bar"></i> 测试报告</h1>
                <p>测试报告生成与展示</p>
                <p>测试执行完毕后, 测试报告会自动生成到 <span class="highlight">report</span> 文件夹下。</p>

            </div>

            <div class="content-card">
                <h2><i class="fas fa-file-code"></i> Yaml Report</h2>
                <div class="code-block">
                    <pre><code>elapsed: 9.003
    error: 0
    failed: 3
    passed: 3
    result: Failed
    skipped: 3
    started_time: '2025-07-021 12:05:37'
    total: 9</code></pre>

                </div>
            </div>
            <div class="content-card">
                <h2><i class="fas fa-images"></i> 测试报告图片示例</h2>
                <div class="report-gallery">
                    <div class="report-card">
                        <div class="report-img" onclick="showImageModal('autoapi/data/images/allure-report.png')">
                            <img src="autoapi/data/images/allure-report.png" alt="Allure报告">
                        </div>
                        <div class="caption">Allure 测试报告</div>
                    </div>

                    <div class="report-card">
                        <div class="report-img" onclick="showImageModal('autoapi/data/images/html-report.png')">
                            <img src="autoapi/data/images/html-report.png" alt="HTML报告">
                        </div>
                        <div class="caption">HTML 测试报告</div>
                    </div>

                    <div class="report-card">
                        <div class="report-img" onclick="showImageModal('autoapi/data/images/ding-report.png')">
                            <img src="autoapi/data/images/ding-report.png" alt="钉钉报告">
                        </div>
                        <div class="caption">钉钉测试报告</div>
                    </div>

                    <div class="report-card">
                        <div class="report-img" onclick="showImageModal('autoapi/data/images/feishu-report.png')">
                            <img src="autoapi/data/images/feishu-report.png" alt="飞书报告">
                        </div>
                        <div class="caption">飞书测试报告</div>
                    </div>

                    <div class="report-card">
                        <div class="report-img" onclick="showImageModal('autoapi/data/images/wechat-report.png')">
                            <img src="autoapi/data/images/wechat-report.png" alt="企微报告">
                        </div>
                        <div class="caption">企业微信测试报告</div>
                    </div>

                    <div class="report-card">
                        <div class="report-img" onclick="showImageModal('autoapi/data/images/email-success.png')">
                            <img src="autoapi/data/images/email-success.png" alt="邮件成功报告">
                        </div>
                        <div class="caption">邮件-成功测试报告</div>
                    </div>

                    <div class="report-card">
                        <div class="report-img" onclick="showImageModal('autoapi/data/images/email-fail.png')">
                            <img src="autoapi/data/images/email-fail.png" alt="邮件失败报告">
                        </div>
                        <div class="caption">邮件-失败测试报告</div>
                    </div>
                </div>
            </div>
        </div>

        <!-- 常见问题 -->
        <div id="faq-template">
            <div class="content-header">
                <h1><i class="fas fa-question-circle"></i> 常见问题</h1>
                <p>使用AutoAPI时可能遇到的问题及解决方案</p>
            </div>

            <div class="content-card">
                <h2><i class="fas fa-exclamation-triangle"></i> 常见问题解答</h2>

                <h3>❓ 添加日志</h3>
                <p>日志内容需要手动写入, 详细示例 demo 中几乎都有体现, 请自行查看或补充</p>

                <h3>❓ 测试报告生成</h3>
                <p>html：自动生成，检查 run 参数是否开启 html 报告创建, 默认开启</p>
                <p>yaml：自动生成，它目前仅用于测试结果统计</p>
                <p>allure：自动生成，默认开启</p>

                <h3>❓ 调试 yaml 数据</h3>
                <p>在 run() 方法中，有一个 pydantic_verify 参数，它的作用是在运行前对所有 yaml
                    数据结构进行快速校验，如果检验异常，则会抛出异常，如果看不懂
                    pydantic
                    错误信息，建议设置
                    pydantic_verify=False</p>

                <h3>❓ 调试用例</h3>
                <p>你只需在 run() 方法中添加指定测试路径参数，E.g. run(case_path='test_demo.py::test_demo')
                </p>
                <p>注意：由于数据驱动，它不能单独指定某条用例数据，最简单的方式是，注释 yaml 测试数据或使用第三方接口工具进行接口调试</p>

                <h3>❓ 添加运行参数</h3>
                <p>在 run() 方法中，支持任意 *args 和 **kwargs 参数，它们会被转化为 pytest 命令行参数，请遵循 pytest
                    运行参数规范
                </p>
                <p>注意：使用键值形式入参时，如果键值与预设参数重复，将覆盖预设参数</p>

                <h3>❔ 其他问题</h3>
                <p>请确保你已经完全阅读了本文档所有内容，如仍有问题，请创建 ISSUES</p>
            </div>
        </div>
    </div>
    <div id="imageModal" class="image-modal">
        <img class="modal-content" id="modalImage">
    </div>
    <script>
        // 模块内容存储
        const moduleTemplates = {
            home: document.getElementById('home-template').innerHTML,
            structure: document.getElementById('structure-template').innerHTML,
            'quick-start': document.getElementById('quick-start-template').innerHTML,
            hooks: document.getElementById('hooks-template').innerHTML,
            'testcase-gen': document.getElementById('testcase-gen-template').innerHTML,
            reports: document.getElementById('reports-template').innerHTML,
            faq: document.getElementById('faq-template').innerHTML,
            'config-basic': document.getElementById('config-basic-template').innerHTML,
            'config-database': document.getElementById('config-database-template').innerHTML,
            'config-notification': document.getElementById('config-notification-template').innerHTML,
            'test-data-intro': document.getElementById('test-data-intro-template').innerHTML,
            'test-data-params': document.getElementById('test-data-params-template').innerHTML,
            'test-data-example': document.getElementById('test-data-example-template').innerHTML,
            'auth': document.getElementById('auth-template').innerHTML,
        };

        // 菜单切换功能
        document.querySelectorAll(".submenu-toggle").forEach((toggle) => {
            toggle.addEventListener("click", function (e) {
                e.preventDefault();
                const submenu = this.nextElementSibling;
                const arrow = this.querySelector(".menu-arrow");

                submenu.classList.toggle("open");
                arrow.classList.toggle("open");
            });
        });

        // 移动端菜单切换
        document.getElementById("menuToggle").addEventListener("click", function () {
            document.getElementById("sidebar").classList.toggle("open");
        });
        // 切换按钮事件
        document.getElementById("mainContent").addEventListener("click", function (e) {
            const btn = e.target.closest(
                ".toggle-file-btn, .toggle-db-btn, .toggle-notify-btn, .toggle-project-btn"
            );
            if (!btn) return;

            /**
             * 通用切换函数
             * @param {string} buttonClass 按钮 class
             * @param {string} dataAttr 按钮 data-属性名称
             * @param {object} showMap 按钮 value -> 内容区 selector 映射
             * @param {string} containerSelector 容器选择器
             */
            function toggle(buttonClass, dataAttr, showMap, containerSelector) {
                if (!btn.classList.contains(buttonClass)) return false;

                // 找到容器（优先用 containerSelector，否则找最近的 content-card）
                let container = btn.closest(containerSelector) || btn.closest(".content-card") || document;

                // 切换按钮 active
                const buttons = container.querySelectorAll(`.${buttonClass}`);
                buttons.forEach(b => b.classList.remove("active"));
                btn.classList.add("active");

                // 根据按钮 data 属性切换显示内容
                const value = btn.getAttribute(dataAttr);
                Object.keys(showMap).forEach(key => {
                    const selector = showMap[key];
                    const block = container.querySelector(selector);
                    if (block) block.style.display = key === value ? "block" : "none";
                });

                return true;
            }

            // 1️⃣ YAML/JSON 切换
            toggle("toggle-file-btn", "data-type", {
                yaml: ".code-yaml",
                json: ".code-json"
            }, ".content-card");

            // 2️⃣ 全部参数 / 特殊参数切换
            toggle("toggle-file-btn", "data-type", {
                all_params: ".table-container.all_params",
                special_params: ".table-container.special_params"
            }, ".content-card");

            // 3️⃣ MySQL/Redis 切换
            toggle("toggle-db-btn", "data-db", {
                mysql: ".db-mysql",
                redis: ".db-redis"
            }, ".content-card");

            // 4️⃣ 项目结构切换
            toggle("toggle-project-btn", "data-project", {
                directory: ".directory-structure",
                projects: ".projects-concept"
            }, ".content-card");

            // 5️⃣ 通知方式切换
            toggle("toggle-notify-btn", "data-type", {
                email: ".notify-email",
                dingding: ".notify-dingding",
                feishu: ".notify-feishu",
                wechat: ".notify-wechat"
            }, ".content-card");
        });


        // 加载模块内容
        function loadModule(moduleName) {
            const contentArea = document.getElementById("mainContent");
            contentArea.innerHTML = `
                <div class="content-loading">
                    <i class="fas fa-spinner fa-spin"></i>
                    <p>加载内容中...</p>
                </div>
            `;

            // 模拟加载延迟
            setTimeout(() => {
                if (moduleTemplates[moduleName]) {
                    contentArea.innerHTML = moduleTemplates[moduleName];

                    // 更新URL
                    window.history.pushState({ module: moduleName }, "", `#${moduleName}`);
                } else {
                    contentArea.innerHTML = `
                        <div class="content-header">
                            <h1><i class="fas fa-exclamation-triangle"></i> 加载失败</h1>
                            <p>无法加载 ${moduleName} 模块内容</p>
                        </div>
                        <div class="content-card">
                            <div class="alert alert-danger">
                                <i class="fas fa-exclamation-circle"></i>
                                <div>
                                    <strong>模块加载错误</strong>
                                    <p>请检查模块名称是否正确</p>
                                </div>
                            </div>
                            <a href="#" class="btn" data-module="home"><i class="fas fa-home"></i> 返回首页</a>
                        </div>
                    `;
                }
            }, 300);
        }

        // 设置活动菜单项
        function setActiveMenuItem(moduleName) {
            document.querySelectorAll(".menu-link, .submenu-link").forEach((item) => {
                item.classList.remove("active");

                if (item.getAttribute("data-module") === moduleName) {
                    item.classList.add("active");

                    // 展开父菜单
                    const submenuToggle = item.closest(".submenu")?.previousElementSibling;
                    if (submenuToggle) {
                        const submenu = submenuToggle.nextElementSibling;
                        const arrow = submenuToggle.querySelector(".menu-arrow");

                        submenu.classList.add("open");
                        arrow.classList.add("open");
                    }
                }
            });
        }

        // 菜单点击事件
        document.querySelectorAll(".menu-link, .submenu-link").forEach((link) => {
            link.addEventListener("click", function (e) {
                e.preventDefault();

                const moduleName = this.getAttribute("data-module");
                if (moduleName) {
                    // 设置活动菜单项
                    setActiveMenuItem(moduleName);

                    // 加载模块内容
                    loadModule(moduleName);

                    // 在移动端点击菜单后关闭侧边栏
                    if (window.innerWidth < 992) {
                        document.getElementById("sidebar").classList.remove("open");
                    }
                }
            });
        });

        // 初始加载首页内容
        const initialModule = window.location.hash
            ? window.location.hash.substring(1)
            : "home";
        loadModule(initialModule);
        setActiveMenuItem(initialModule);

        // 内容区域内的按钮点击事件（委托）
        document.getElementById("mainContent").addEventListener("click", function (e) {
            if (e.target.matches(".btn[data-module]")) {
                e.preventDefault();
                const moduleName = e.target.getAttribute("data-module");

                // 设置活动菜单项
                setActiveMenuItem(moduleName);

                // 加载模块内容
                loadModule(moduleName);
            }
        });


        // 处理浏览器后退/前进
        window.addEventListener("popstate", function (event) {
            if (event.state && event.state.module) {
                setActiveMenuItem(event.state.module);
                loadModule(event.state.module);
            }
        });

        // 处理页面加载时的hash
        window.addEventListener("DOMContentLoaded", () => {
            if (window.location.hash) {
                const moduleName = window.location.hash.substring(1);
                setActiveMenuItem(moduleName);
                loadModule(moduleName);
            }
        });

        // 图片模态框功能
        let scale = 1;

        function showImageModal(imgSrc) {
            const modal = document.getElementById("imageModal");
            const img = document.getElementById("modalImage");

            img.src = imgSrc;
            scale = 1;
            img.style.transform = `scale(${scale})`;

            modal.classList.add("open");
            document.body.style.overflow = "hidden"; // 禁止背景滚动

            // 鼠标滚轮缩放
            modal.onwheel = function (e) {
                e.preventDefault();
                const delta = Math.sign(e.deltaY);
                scale -= delta * 0.1;
                scale = Math.min(Math.max(scale, 0.5), 3); // 限制缩放范围 0.5 ~ 3
                img.style.transform = `scale(${scale})`;
            };

            // 点击图片或背景关闭
            modal.onclick = function (e) {
                if (e.target === modal || e.target === img) {
                    modal.classList.remove("open");
                    document.body.style.overflow = ""; // 恢复背景滚动
                }
            };
        }

    </script>
</body>

</html>